﻿using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using NLog;

namespace TfsGiggle.Core
{
    /// <summary>
    /// Tfs source control helper class
    /// </summary>
    public sealed class TfsSourceControl : IDisposable
    {
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        private static readonly Lazy<TfsSourceControl> _instance = new Lazy<TfsSourceControl>(() => new TfsSourceControl());
        private ConcurrentDictionary<string, Tuple<TfsTeamProjectCollection, VersionControlServer>> _tfsTeamProjects = new ConcurrentDictionary<string, Tuple<TfsTeamProjectCollection, VersionControlServer>>();
        private TfsConfigurationServer _tfs;
        private Uri _tfsUri;

        private TfsSourceControl() { }

        /// <summary>
        /// Tfs source control instance
        /// </summary>
        public static TfsSourceControl Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        /// <summary>
        /// Opens a connection to a TfsConfigurationServer
        /// </summary>
        /// <param name="tfsUri"></param>
        /// <returns></returns>
        internal bool Open(Uri tfsUri)
        {
            if (tfsUri == null) throw new ArgumentNullException("tfsUri");
            _logger.Trace("tfsUri:{0}", tfsUri);

            _tfsUri = tfsUri;
            _tfs = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);
            if (_tfs == null)
            {
                _logger.Error("The uri:{0} could not be used to connect to Tfs", tfsUri);
                return false;
            }
            _tfs.EnsureAuthenticated();
            return true;
        }

        /// <summary>
        /// Gets a version control server
        /// </summary>
        /// <param name="teamProjectCollectionId"></param>
        /// <returns></returns>
        internal VersionControlServer GetVersionControlServer(string teamProjectCollectionId)
        {
            Tuple<TfsTeamProjectCollection, VersionControlServer> tfsAndVcs;
            _logger.Trace("teamProjectCollectionId:{0}", teamProjectCollectionId);

            if (!_tfsTeamProjects.TryGetValue(teamProjectCollectionId, out tfsAndVcs))
            {
                var tfs = _tfs.GetTeamProjectCollection(new Guid(teamProjectCollectionId));
                tfs.EnsureAuthenticated();
                tfsAndVcs = new Tuple<TfsTeamProjectCollection, VersionControlServer>(tfs, tfs.GetService<VersionControlServer>());
                _tfsTeamProjects.AddOrUpdate(teamProjectCollectionId, tfsAndVcs, (key, value) => tfsAndVcs);
            }
            return tfsAndVcs.Item2;
        }

        /// <summary>
        /// Get the specific file in the specified teamprojectcollection
        /// </summary>
        /// <param name="teamProjectCollectionId"></param>
        /// <param name="serverPath"></param>
        /// <returns></returns>
        internal string GetFile(string teamProjectCollectionId, string serverPath)
        {
            if (string.IsNullOrWhiteSpace(teamProjectCollectionId)) throw new ArgumentNullException("teamProjectCollectionId");
            if (string.IsNullOrWhiteSpace(serverPath)) throw new ArgumentNullException("serverPath");

            _logger.Trace("teamProjectCollectionId:{0} serverPath:{1}", teamProjectCollectionId, serverPath);

            string fileContent = null;
            try
            {
                var vcs = GetVersionControlServer(teamProjectCollectionId);
                if (vcs == null)
                {
                    return fileContent;
                }
                if (vcs.ServerItemExists(serverPath, ItemType.File))
                {
                    _logger.Trace("file '{0}' found", serverPath);
                    var item = vcs.GetItem(serverPath);
                    var encoding = Encoding.GetEncoding(item.Encoding);
                    _logger.Trace("Encoding '{0}' is '{1}'", item.Encoding, encoding);

                    using (var sr = new StreamReader(item.DownloadFile(), encoding, true))
                    {
                        fileContent = sr.ReadToEnd();
                        _logger.Trace("fileContent read");
                    }
                }
                else
                {
                    _logger.Warn("file {0} does not exist in {1}", serverPath, teamProjectCollectionId);
                }
            }
            catch (TeamFoundationServiceUnavailableException ex)
            {
                _logger.ErrorException("Tfs is currently unavailable. Please contact your Tfs admin, and then retry the operation when Tfs is available again. {0}", ex);
            }
            catch (Exception ex)
            {
                _logger.ErrorException("failed with exception", ex);
            }
            return fileContent;
        }

        private void Dispose(bool disposing)
        {
            if (disposing && _tfs != null)
            {
                _tfs.Dispose();
                _tfs = null;
            }
            if (disposing && _tfsTeamProjects != null && _tfsTeamProjects.Count > 0)
            {
                foreach (var tfs in _tfsTeamProjects.Values)
                {
                    tfs.Item1.Dispose();
                }
                _tfsTeamProjects = null;
            }
        }

        /// <summary>
        /// Closes the Tfs connection
        /// </summary>
        public void Dispose()
        {
            _logger.Trace("enter");
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}